#![allow(
    clippy::too_many_arguments,
    clippy::cognitive_complexity,
    clippy::wrong_self_convention,
    unused_qualifications
)]

#[macro_use]
mod macros;
mod aliases;
pub use aliases::*;
mod bitflags;
pub use bitflags::*;
#[cfg(feature = "debug")]
mod const_debugs;
mod constants;
pub use constants::*;
mod definitions;
pub use definitions::*;
mod enums;
pub use enums::*;
mod extensions;
pub use extensions::*;
mod feature_extensions;
mod features;
pub use features::*;
/// Native bindings from Vulkan headers, generated by bindgen
#[allow(clippy::useless_transmute, nonstandard_style)]
pub mod native;
mod platform_types;
pub use platform_types::*;

pub trait Handle: Sized {
    const TYPE: ObjectType;
    fn as_raw(self) -> u64;
    fn from_raw(_: u64) -> Self;

    /// Returns whether the handle is a `NULL` value.
    ///
    /// # Example
    ///
    /// ```
    /// # use ash::vk::{Handle, Instance};
    /// let instance = Instance::null();
    /// assert!(instance.is_null());
    /// ```
    fn is_null(self) -> bool {
        self.as_raw() == 0
    }
}

/// Iterates through the pointer chain. Includes the item that is passed into the function. Stops at
/// the last [`BaseOutStructure`] that has a null [`BaseOutStructure::p_next`] field.
pub(crate) unsafe fn ptr_chain_iter<'a, T: TaggedStructure<'a>>(
    ptr: &mut T,
) -> impl Iterator<Item = *mut BaseOutStructure<'_>> {
    let ptr = <*mut T>::cast::<BaseOutStructure<'_>>(ptr);
    (0..).scan(ptr, |p_ptr, _| {
        if p_ptr.is_null() {
            return None;
        }
        let n_ptr = (**p_ptr).p_next;
        let old = *p_ptr;
        *p_ptr = n_ptr;
        Some(old)
    })
}

/// Structures implementing this trait are layout-compatible with [`BaseInStructure`] and
/// [`BaseOutStructure`]. Such structures have an `s_type` field indicating its type, which must
/// always match the value of [`TaggedStructure::STRUCTURE_TYPE`].
pub unsafe trait TaggedStructure<'a>: Sized {
    const STRUCTURE_TYPE: StructureType;

    /// Prepends the given extension struct between the root and the first pointer. This method is
    /// only available on structs that can be passed to a function directly. Only valid extension
    /// structs can be pushed into the chain.
    /// If the chain looks like `A -> B -> C`, and you call `A.push(&mut D)`, then the
    /// chain will look like `A -> D -> B -> C`.
    ///
    /// # Panics
    /// If `next` contains a pointer chain of its own, this function will panic.  Call `unsafe`
    /// [`Self::extend()`] to insert this chain instead.
    fn push<'b: 'a, T: Extends<Self> + TaggedStructure<'b>>(mut self, next: &'a mut T) -> Self {
        // SAFETY: All implementers of `TaggedStructure` are required to have the `BaseOutStructure` layout
        let slf_base = unsafe { &mut *<*mut _>::cast::<BaseOutStructure<'_>>(&mut self) };
        // SAFETY: All implementers of `T: TaggedStructure` are required to have the `BaseOutStructure` layout
        let next_base = unsafe { &mut *<*mut T>::cast::<BaseOutStructure<'_>>(next) };
        // `next` here can contain a pointer chain.  This function refuses to insert the struct,
        // in favour of calling unsafe extend().
        assert!(
            next_base.p_next.is_null(),
            "push() expects a struct without an existing p_next pointer chain (equal to NULL)"
        );
        next_base.p_next = slf_base.p_next;
        slf_base.p_next = next_base;
        self
    }

    /// Prepends the given extension struct between the root and the first pointer. This method is
    /// only available on structs that can be passed to a function directly. Only valid extension
    /// structs can be pushed into the chain.
    /// If the chain looks like `A -> B -> C` and `D -> E`, and you call `A.extend(&mut D)`,
    /// then the chain will look like `A -> D -> E -> B -> C`.
    ///
    /// # Safety
    /// This function will walk the [`BaseOutStructure::p_next`] chain of `next`, requiring
    /// all non-`NULL` pointers to point to a valid Vulkan structure starting with the
    /// [`BaseOutStructure`] layout.
    ///
    /// The last struct in this chain (i.e. the one where `p_next` is `NULL`) must be writable
    /// memory, as its `p_next` field will be updated with the value of `self.p_next`.
    unsafe fn extend<'b: 'a, T: Extends<Self> + TaggedStructure<'b>>(
        mut self,
        next: &'a mut T,
    ) -> Self {
        // `next` here can contain a pointer chain. This means that we must correctly attach he head
        // to the root and the tail to the rest of the chain For example:
        //
        // next = A -> B
        // Before: `Root -> C -> D -> E`
        // After: `Root -> A -> B -> C -> D -> E`
        //                 ^^^^^^
        //                 next chain
        let slf_base = unsafe { &mut *<*mut _>::cast::<BaseOutStructure<'_>>(&mut self) };
        let next_base = <*mut T>::cast::<BaseOutStructure<'_>>(next);
        let last_next = ptr_chain_iter(next).last().unwrap();
        (*last_next).p_next = slf_base.p_next;
        slf_base.p_next = next_base;
        self
    }
}

/// Implemented for every structure that extends base structure `B`. Concretely that means struct
/// `B` is listed in its array of [`structextends` in the Vulkan registry][1].
///
/// Similar to [`TaggedStructure`], all `unsafe` implementers of this trait must guarantee that
/// their structure is layout-compatible [`BaseInStructure`] and [`BaseOutStructure`].
///
/// [1]: https://registry.khronos.org/vulkan/specs/latest/styleguide.html#extensions-interactions
pub unsafe trait Extends<B> {}

/// Holds 24 bits in the least significant bits of memory,
/// and 8 bytes in the most significant bits of that memory,
/// occupying a single [`u32`] in total. This is commonly used in
/// [acceleration structure instances] such as
/// [`AccelerationStructureInstanceKHR`],
/// [`AccelerationStructureSRTMotionInstanceNV`] and
/// [`AccelerationStructureMatrixMotionInstanceNV`].
///
/// [acceleration structure instances]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkAccelerationStructureInstanceKHR.html#_description
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[repr(transparent)]
pub struct Packed24_8(u32);

impl Packed24_8 {
    pub fn new(low_24: u32, high_8: u8) -> Self {
        Self((low_24 & 0x00ff_ffff) | (u32::from(high_8) << 24))
    }

    /// Extracts the least-significant 24 bits (3 bytes) of this integer
    pub fn low_24(&self) -> u32 {
        self.0 & 0xffffff
    }

    /// Extracts the most significant 8 bits (single byte) of this integer
    pub fn high_8(&self) -> u8 {
        (self.0 >> 24) as u8
    }
}

impl ColorComponentFlags {
    /// Contraction of [`R`][Self::R] | [`G`][Self::G] | [`B`][Self::B] | [`A`][Self::A]
    pub const RGBA: Self = Self(Self::R.0 | Self::G.0 | Self::B.0 | Self::A.0);
}

impl From<Extent2D> for Extent3D {
    fn from(value: Extent2D) -> Self {
        Self {
            width: value.width,
            height: value.height,
            depth: 1,
        }
    }
}

impl From<Extent2D> for Rect2D {
    fn from(extent: Extent2D) -> Self {
        Self {
            offset: Default::default(),
            extent,
        }
    }
}

#[inline]
pub(crate) fn wrap_c_str_slice_until_nul(
    str: &[core::ffi::c_char],
) -> core::result::Result<&core::ffi::CStr, core::ffi::FromBytesUntilNulError> {
    // SAFETY: The cast from c_char to u8 is ok because a c_char is always one byte.
    let bytes = unsafe { core::slice::from_raw_parts(str.as_ptr().cast(), str.len()) };
    core::ffi::CStr::from_bytes_until_nul(bytes)
}

#[derive(Debug)]
pub struct CStrTooLargeForStaticArray {
    pub static_array_size: usize,
    pub c_str_size: usize,
}
#[cfg(feature = "std")]
impl std::error::Error for CStrTooLargeForStaticArray {}
impl core::fmt::Display for CStrTooLargeForStaticArray {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "static `c_char` target array of length `{}` is too small to write a `CStr` (with `NUL`-terminator) of length `{}`",
            self.static_array_size, self.c_str_size
        )
    }
}

#[inline]
pub(crate) fn write_c_str_slice_with_nul(
    target: &mut [core::ffi::c_char],
    str: &core::ffi::CStr,
) -> core::result::Result<(), CStrTooLargeForStaticArray> {
    let bytes = str.to_bytes_with_nul();
    // SAFETY: The cast from c_char to u8 is ok because a c_char is always one byte.
    let bytes = unsafe { core::slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len()) };
    let static_array_size = target.len();
    target
        .get_mut(..bytes.len())
        .ok_or(CStrTooLargeForStaticArray {
            static_array_size,
            c_str_size: bytes.len(),
        })?
        .copy_from_slice(bytes);
    Ok(())
}

#[cfg(feature = "debug")]
pub(crate) fn debug_flags<Value: Into<u64> + Copy>(
    f: &mut core::fmt::Formatter<'_>,
    known: &[(Value, &'static str)],
    value: Value,
) -> core::fmt::Result {
    let mut first = true;
    let mut accum = value.into();
    for &(bit, name) in known {
        let bit = bit.into();
        if bit != 0 && accum & bit == bit {
            if !first {
                f.write_str(" | ")?;
            }
            f.write_str(name)?;
            first = false;
            accum &= !bit;
        }
    }
    if accum != 0 {
        if !first {
            f.write_str(" | ")?;
        }
        write!(f, "{accum:b}")?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use crate::vk;
    use crate::vk::TaggedStructure as _;
    use alloc::vec::Vec;
    #[test]
    fn test_ptr_chains() {
        let mut variable_pointers = vk::PhysicalDeviceVariablePointerFeatures::default();
        let mut corner = vk::PhysicalDeviceCornerSampledImageFeaturesNV::default();
        let chain = alloc::vec![
            <*mut _>::cast(&mut variable_pointers),
            <*mut _>::cast(&mut corner),
        ];
        let mut device_create_info = vk::DeviceCreateInfo::default()
            .push(&mut corner)
            .push(&mut variable_pointers);
        let chain2: Vec<*mut vk::BaseOutStructure<'_>> = unsafe {
            vk::ptr_chain_iter(&mut device_create_info)
                .skip(1)
                .collect()
        };
        assert_eq!(chain, chain2);
    }

    #[test]
    #[should_panic]
    fn disallow_nested_ptr_chains() {
        let mut generated_commands =
            vk::PhysicalDeviceDeviceGeneratedCommandsFeaturesEXT::default();
        let mut private_data = vk::PhysicalDevicePrivateDataFeatures {
            p_next: <*mut _>::cast(&mut generated_commands),
            ..Default::default()
        };
        let _device_create_info = vk::DeviceCreateInfo::default().push(&mut private_data);
    }

    #[test]
    fn test_nested_ptr_chains() {
        let mut generated_commands =
            vk::PhysicalDeviceDeviceGeneratedCommandsFeaturesEXT::default();
        let mut private_data = vk::PhysicalDevicePrivateDataFeatures {
            p_next: <*mut _>::cast(&mut generated_commands),
            ..Default::default()
        };
        let mut variable_pointers = vk::PhysicalDeviceVariablePointerFeatures::default();
        let mut corner = vk::PhysicalDeviceCornerSampledImageFeaturesNV::default();
        let chain = alloc::vec![
            <*mut _>::cast(&mut private_data),
            <*mut _>::cast(&mut generated_commands),
            <*mut _>::cast(&mut variable_pointers),
            <*mut _>::cast(&mut corner),
        ];
        let mut device_create_info = vk::DeviceCreateInfo::default()
            .push(&mut corner)
            .push(&mut variable_pointers);
        // Insert private_data->generated_commands into the chain, such that generate_commands->variable_pointers->corner:
        device_create_info = unsafe { device_create_info.extend(&mut private_data) };
        let chain2: Vec<*mut vk::BaseOutStructure<'_>> = unsafe {
            vk::ptr_chain_iter(&mut device_create_info)
                .skip(1)
                .collect()
        };
        assert_eq!(chain, chain2);
    }

    #[test]
    fn test_use_struct_after_pointer_chain() {
        // The negative case of this test, where `pdev_props` stays alive by being
        // used at the end of this function while `api` is invalidly accessed first
        // (resulting in an immutable borrow while mutably borrowed error), exists in
        // `tests/fail/long_lived_root_struct_borrow.rs`.  This test demonstrates the expected usage
        // pattern of dropping `pdev_props` so that `api` no longer becomes mutably borrowed and the
        // properties read from Vulkan can now be accessed by the caller.

        let mut layers = vec![];
        let mut api =
            vk::PhysicalDeviceLayeredApiPropertiesListKHR::default().layered_apis(&mut layers);
        let _pdev_props = vk::PhysicalDeviceProperties2::default().push(&mut api);

        // Access to either variable is allowed because `pdev_props` is no longer used
        dbg!(&api);
        dbg!(&layers);
    }

    #[test]
    fn test_debug_flags() {
        assert_eq!(
            format!(
                "{:?}",
                vk::AccessFlags::INDIRECT_COMMAND_READ | vk::AccessFlags::VERTEX_ATTRIBUTE_READ
            ),
            "INDIRECT_COMMAND_READ | VERTEX_ATTRIBUTE_READ"
        );
    }

    #[test]
    fn test_debug_enum() {
        assert_eq!(format!("{:?}", vk::ChromaLocation::MIDPOINT), "MIDPOINT");
    }
}
